/*
 * Copyright 2010 dev7.jp
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jp.dev7.android.worker;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

/**
 * バックグラウンドで動作するワーカー。
 * 
 * @param <RESULT>
 *            処理結果の型
 * @author narazaki.daishi
 */
public abstract class Worker<RESULT> {

    /** 処理結果のメッセージキー. */
    public static final String RESULT = "jp.dev7.android.worker.Worker.RESULT";

    /** 処理実行中に発生した例外のメッセージキー. */
    public static final String EXCEPTION =
        "jp.dev7.android.worker.Worker.EXCEPTION";

    /** 実行済み. */
    private boolean executed;

    /** バックグラウンドスレッド. */
    private Thread thread;

    /** フォアグラウンドスレッドに処理完了を伝えるハンドラ. */
    private Handler handler;

    /** 処理結果. */
    private RESULT result;

    /** 処理中に発生した例外. */
    private Exception exception;

    /** cancel()メソッドが呼ばれたか. */
    private boolean canceled;

    /**
     * バックグラウンド処理をキャンセルする。
     */
    public void cancel() {
        this.canceled = true;
    }

    /**
     * バックグラウンド処理本体。
     * 
     * このメソッドはexecute()メソッドによってバックグラウンドスレッドで実行される。<br />
     * 処理結果、および処理中で発生した例外は、処理後にhandlerへ送られるメッセージ内に格納される。<br />
     * 
     * @return 処理結果
     * @throws Exception
     *             -
     */
    protected abstract RESULT doInBackground() throws Exception;

    /**
     * バックグラウンド処理終了後の処理。
     * 
     * 戻り値として、handlerに送信するメッセージを返す。<br />
     * メッセージ内に、RESULTをキーとして処理結果を格納することを推奨。<br />
     * 処理結果はgetResult()、処理中に発生した例外はgetException()で参照可能。<br />
     * 
     * @return handlerに送信するメッセージ
     */
    protected abstract Message done();

    /**
     * バックグラウンド処理を実行する。
     * 
     * ハンドラがnullの場合、NullPointerExceptionを投げる。
     * 
     * @param handler
     *            フォアグラウンドスレッドに処理完了を伝えるハンドラ
     */
    public void execute(Handler handler) {

        // 一度しか実行できない
        if (executed) {
            throw new IllegalStateException("worker was already executed");
        }
        executed = true;

        // handlerは必須
        if (handler == null) {
            throw new NullPointerException("handler cannot be null");
        }

        this.handler = handler;

        thread = new Thread() {
            public void run() {
                Log.i("dev7", "worker thread start");

                try {

                    // バックグラウンド処理実行
                    RESULT result = doInBackground();

                    // 処理結果を保存
                    setResult(result);

                } catch (Exception e) {
                    Log.i("dev7", "worker thread catch exception", e);

                    // 例外を保存
                    setException(e);

                } finally {

                    // 処理後処理実行
                    Message message = done();

                    // done()の戻り値がnullだった場合
                    if (message == null) {
                        message = new Message();
                        message.setData(new Bundle());
                    }

                    // 例外が発生していた場合はメッセージ内に例外を格納
                    if (getException() != null) {
                        message.getData().putSerializable(
                            EXCEPTION,
                            getException());
                    }

                    // ハンドラに処理の終了を通知
                    Worker.this.handler.sendMessage(message);

                    Log.i("dev7", "worker thread end");
                }
            }
        };

        // スレッド開始
        thread.start();
    }

    /**
     * @return the exception
     */
    public Exception getException() {
        return exception;
    }

    /**
     * @return 処理結果
     */
    public RESULT getResult() {
        return result;
    }

    /**
     * @return すでにキャンセル済み
     */
    public boolean isCanceled() {
        return canceled;
    }

    /**
     * @param exception
     *            the exception to set
     */
    public void setException(Exception exception) {
        this.exception = exception;
    }

    /**
     * @param result
     *            処理結果
     */
    private void setResult(RESULT result) {
        this.result = result;
    }

}
